
#include "HttpUtil.h"
#include "StrUtil.h"
#include "FileUtil.h"
#include "SysUtil.h"
#include "../../httplib/httplib.h"

using namespace x2lib;

#define HTTP_HEAD 	"POST %s HTTP/1.1\r\n"\
					"Host: %s\r\n"\
					"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:59.0) Gecko/20100101 Firefox/59.0\r\n"\
					"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"\
					"Accept-Language: en-US,en;q=0.5\r\n"\
					"Accept-Encoding: gzip, deflate\r\n"\
					"Content-Type: multipart/form-data; boundary=%s\r\n"\
					"Content-Length: %ld\r\n"\
					"Connection: close\r\n"\
					"Upgrade-Insecure-Requests: 1\r\n"\
					"DNT: 1\r\n\r\n"\

#define UPLOAD_REQUEST	"--%s\r\n"\
						"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"\
						"Content-Type: application/octet-stream\r\n\r\n"

bool HttpUtil::Download(const char szUrl[], uint8_t *pData, int *pnData, int iStart, int iEnd)
{
	std::map<char*, char*> mapTemp;
	return DoRequest(szUrl, mapTemp, pData, pnData, "GET", iStart, iEnd);
}

bool HttpUtil::Upload(const char szUrl[], char szFileName[], uint8_t *pData, int nData, int iStart, int iEnd)
{
	int cfd = socket(AF_INET, SOCK_STREAM, 0);  //创建socket套接字
	if (-1 == cfd) return false;

	int nPort = 80;
	char szWWW[256] = { 0 };
	char szPath[256] = { 0 };
	bool isHttps;
	in_addr ia = { 0 };
	if (!ParseWebUrl(szUrl, &ia, szWWW, szPath, &nPort, &isHttps))
		return false;

	struct sockaddr_in s_add = { 0 };
	s_add.sin_family = AF_INET;
	s_add.sin_port = htons(nPort);
	s_add.sin_addr = ia;

	if (-1 == connect(cfd, (struct sockaddr *)(&s_add), sizeof(struct sockaddr)))  //建立TCP连接
	{
		return false;
	}

	char szBoundary[128] = { 0 };
	sprintf(szBoundary, "---------------------------%lld", SysUtil::GetCurrentTick(true));

	char szSegBegin[1024] = {0};
	uint32_t nSegBegin = sprintf(szSegBegin, UPLOAD_REQUEST, szBoundary, szFileName);
	char szSegEnd[128] = { 0 };
	uint32_t nSegEnd = sprintf(szSegEnd, "\r\n--%s--\r\n", szBoundary);
	uint32_t nRequest = nData + nSegBegin + nSegEnd;

	char szHeader[1024] = {0};
	uint32_t nHeader = sprintf(szHeader, HTTP_HEAD, szPath, szWWW, szBoundary, nRequest); //头信息
	nRequest += nHeader;

	char* pRequest = (char*)calloc(1, nRequest);	//申请内存用于存放要发送的数据

	/******* 拼接http字节流信息 *********/
	sprintf(pRequest, "%s%s", szHeader, szSegBegin);
	memcpy(pRequest + nHeader + nSegBegin, pData, nData);
	memcpy(pRequest + nHeader + nSegBegin + nData, szSegEnd, nSegEnd);  //http结束信息

	/*********  发送http 请求 ***********/
	if (-1 == send(cfd, pRequest, nRequest, 0))
	{
		free(pRequest);
		return false;
	}

	/*********  接受http post 回复的json信息 ***********/
#if 0
	char *pResponse = (char*)calloc(1,1024*10);
	recv(cfd, pResponse, 1024*10, 0);
#endif

	free(pRequest);
#ifdef __X2LIB_WIN32__
	closesocket(cfd);
#else
	close(cfd);
#endif

	return true;
}

bool HttpUtil::DoRequest(const char szUrl[], std::map<char*, char*> &mapParams, uint8_t *pData, int *pnData, const char *pMethod, int iStart, int iEnd)
{
	int nPort = 80;
	char szWWW[256] = { 0 };
	char *pPath = (char*)calloc(1, strlen(szUrl));
	bool isHttps;
	in_addr ia = { 0 };
	if (!ParseWebUrl(szUrl, &ia, szWWW, pPath, &nPort, &isHttps))
	{
		free(pPath);
		return false;
	}
	std::string strPath = pPath;
	free(pPath);

	std::map<char*, char*>::iterator it = mapParams.begin();
	for (; it != mapParams.end(); it++)
	{
		if (std::string::npos == strPath.find('?'))
			strPath += "?";
		else
			strPath += "&";
		strPath += it->first;
		strPath += "=";
		strPath += it->second;
	}

	int fd = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(nPort);
	addr.sin_addr = ia;

	if (connect(fd, (struct sockaddr *)(&addr), sizeof(addr)) == -1)/*连接网站*/
	{
#ifdef __X2LIB_WIN32__
		closesocket(fd);
#else
		close(fd);
#endif
		return false;
	}
	char szRange[128] = { 0 };
	if (iEnd > 0)
	{
		sprintf(szRange, "Range: bytes = %d-%d", iStart, iEnd);
	}
	else
	{
		sprintf(szRange, "Range: bytes = %d-", iStart);
	}
	char szRequest[2048];
	sprintf(szRequest, "%s %s HTTP/1.1\r\n"
		"Accept: */*\r\n"
		"Accept-Language: zh-cn\r\n"
		"User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n"
		"Host: %s:%d\r\n"
		"Connection: Close\r\n"
		"%s\r\n"
		"\r\n", pMethod, strPath.c_str(), szWWW, nPort, szRange);

	int nLen = strlen(szRequest);
	do
	{
		// szRequest的大小只有1024，如果url过大会导致崩溃在此处
		int n = send(fd, szRequest, nLen, 0);
		if (n < 1)
		{
#ifdef __X2LIB_WIN32__
			closesocket(fd);
#else
			close(fd);
#endif
			return false;
		}
		nLen -= n;
	} while (nLen>0);

	int nData = *pnData;
	*pnData = 0;
	do
	{
		// 注意如果响应头包含Transfer-Encoding: chunked，则数据最前面一行是数据的大小，且数据过大会分块传输，
		// 每块数据最前面的一行都是当前块的大小
		char s[1024] = {0}; // 容量大于响应头
		int n = recv(fd, s, sizeof(s), MSG_WAITALL);
		int nRemained = nData - (*pnData);
		if (pData && (nRemained > 0) && n>0)
		{
			int num = n;
			char *p = s;
			if (*pnData == 0)
			{
				p = strstr(s, "\r\n\r\n");
				if (!p) continue;

				p += 4;
				num = n - (p - s);
			}
			num = (nRemained > num ? num : nRemained);
			memcpy(&pData[*pnData], p, num);
			*pnData += num;
		}
		if (n < 1) break;
	} while (true);
#ifdef __X2LIB_WIN32__
	closesocket(fd);
#else
	close(fd);
#endif

	return true;
}

bool HttpUtil::DoRequest(const char szUrl[], std::map<std::string, std::string>* pmapParams, uint8_t *pData, int *pnData, const char *pMethod, int iStart, int iEnd)
{
    int nPort = 80;
    char szWWW[256] = { 0 };
    char *pPath = (char*)calloc(1, strlen(szUrl));
    bool isHttps;
	in_addr ia = { 0 };
    if (!ParseWebUrl(szUrl, &ia, szWWW, pPath, &nPort, &isHttps))
    {
        free(pPath);
        return false;
    }
    std::string strPath = pPath;
    free(pPath);

    if (pmapParams)
    {
        std::map<std::string, std::string>::iterator it = pmapParams->begin();
        for (; it != pmapParams->end(); it++)
        {
            if (std::string::npos == strPath.find('?'))
                strPath += "?";
            else
                strPath += "&";
            strPath += it->first;
            strPath += "=";
            strPath += it->second;
        }
    }

    int fd = socket(PF_INET, SOCK_STREAM, 0);
	struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(nPort);
    addr.sin_addr = ia;

    if (connect(fd, (struct sockaddr *)(&addr), sizeof(addr)) == -1)/*连接网站*/
    {
#ifdef __X2LIB_WIN32__
		closesocket(fd);
#else
		close(fd);
#endif
        return false;
    }
    char szRange[128] = { 0 };
    if (iEnd > 0)
    {
        sprintf(szRange, "Range: bytes = %d-%d", iStart, iEnd);
    }
    else
    {
        sprintf(szRange, "Range: bytes = %d-", iStart);
    }
	char* pRequest = (char*)calloc(1, strPath.length() + 2048);
    sprintf(pRequest, "%s %s HTTP/1.1\r\n"
        "Accept: */*\r\n"
        "Accept-Language: zh-cn\r\n"
        "User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n"
        "Host: %s:%d\r\n"
        "Connection: Close\r\n"
        "%s\r\n"
        "\r\n", pMethod, strPath.c_str(), szWWW, nPort, szRange);

    int nLen = strlen(pRequest);
    do
    {
        int n = send(fd, pRequest, nLen, 0);
        if (n < 1)
        {
#ifdef __X2LIB_WIN32__
			closesocket(fd);
#else
			close(fd);
#endif
			break;
        }
        nLen -= n;
    } while (nLen > 0);
	free(pRequest);

	if (nLen != 0) { return false; }

    int nData = *pnData;
    *pnData = 0;
    do
    {
        // 注意如果响应头包含Transfer-Encoding: chunked，则数据最前面一行是数据的大小，且数据过大会分块传输，
        // 每块数据最前面的一行都是当前块的大小
        char s[1024] = { 0 }; // 容量大于响应头
        int n = recv(fd, s, sizeof(s), MSG_WAITALL);
        int nRemained = nData - (*pnData);
        if (pData && (nRemained > 0) && n > 0)
        {
            int num = n;
            char *p = s;
            if (*pnData == 0)
            {
                p = strstr(s, "\r\n\r\n");
                if (!p) continue;

                p += 4;
                num = n - (p - s);
            }
            num = (nRemained > num ? num : nRemained);
            memcpy(&pData[*pnData], p, num);
            *pnData += num;
        }
        if (n < 1 || n<sizeof(s)) break;
    } while (true);
#ifdef __X2LIB_WIN32__
	closesocket(fd);
#else
	close(fd);
#endif

    // 判断302跳转
    char szNewUrl[1024] = {0};
    if (Parse302Url((char*)pData, *pnData, szNewUrl, sizeof(szNewUrl)))
    {
        return DoRequest(szNewUrl, pmapParams, pData, pnData, pMethod, iStart, iEnd);
    }

    return true;
}

std::string HttpUtil::DoRequest(const char* pszUrl, int port, std::map<std::string, std::string>* pmapParams, const char *pMethod)
{
    char szWWW[256] = { 0 };
    char *pPath = (char*)calloc(1, strlen(pszUrl));
    bool isHttps;
	in_addr ia = { 0 };
    if (!ParseWebUrl(pszUrl, &ia, szWWW, pPath, &port, &isHttps))
    {
        free(pPath);
        return "";
    }

	httplib::Client client(szWWW, port);
	std::string strContent = client.Get(pPath).value().body;
    free(pPath);
	return strContent;
}

bool HttpUtil::ParseWebUrl(const char *szWebUrl, struct in_addr* pin_addr, char szWWW[], char szPath[], int *pnPort, bool *pisHttps)
{
	int nWebUrl = strlen(szWebUrl);

	char *pWWW = NULL;
	char szHttps[][12] = { "http://", "https://" }; // 下面的for循环要求szWWWs元素必须依照长度从小到大存放
	for (int i = 0; i < sizeof(szHttps) / sizeof(szHttps[0]); i++)
	{
		int a = strlen(szHttps[i]);
		if (szWebUrl == (char*)SysUtil::FindBytes(szWebUrl, nWebUrl, szHttps[i], a, 0))
		{
			pWWW = (char*)szWebUrl + a;
			*pisHttps = (i == 1); // szHttps里必须将https设置为第1个元素
		}
	}
	if (pWWW == NULL)
		return false;

	*pnPort = 80;
	char *pPath = strchr(pWWW, '/');
	char *pPort = strchr(pWWW, ':');
	intz_t nWWW = strlen(pWWW);
	if (pPath && pPort)
	{
		nWWW = MIN((intz_t)pPath, (intz_t)pPort) - (intz_t)pWWW;
		if (pPort < pPath)
		{
			*pnPort = atoi(pPort + 1);
		}
		strcpy(szPath, pPath);
	}
	else
	{
		if (pPath)
		{
			nWWW = (intz_t)pPath - (intz_t)pWWW;
			strcpy(szPath, pPath);
		}
		else if (pPort)
		{
			nWWW = (intz_t)pPort - (intz_t)pWWW;
			*pnPort = atoi(pPort+1);
			szPath[0] = 0;
		}
	}
	memcpy(szWWW, pWWW, nWWW);
	szWWW[nWWW] = 0;

	// 需要先初始化WSADATA data;
	hostent *host = gethostbyname(szWWW);
	if (host == NULL)
	{
		return false;
	}
	*pin_addr = *(struct in_addr*)host->h_addr;

	return true;
}


bool HttpUtil::ParseRequest(char *pData, int nData, char **ppUrl, int *pnUrl, char **ppForm, int *pnForm)
{
	do
	{
		char *p1 = strstr((char*)pData, "/");
		if (!p1) break;
		char *p2 = strstr(p1, " ");
		if (!p2) break;
		*ppUrl = p1;
		*pnUrl = (p2 - p1);

		char *p3 = strstr(p2, "\r\n\r\n");
		if (!p3) break;
		*ppForm = p3 + 4;
		*pnForm = (nData - (*ppForm - pData));

		return true;
	} while (0);

	return false;
}

bool HttpUtil::PackResponse(int nCode, void *pData, int *pnData, int nCapacity)
{
	const char szResFmt[] = "HTTP/1.1 %d %s\r\n"
		"Server: Apache Tomcat/5.0.12\r\n"
		"Date: %s GMT\r\n"
		"Content-Length:%d\r\n\r\n";

	if ((sizeof(szResFmt) + (*pnData) + 32) > nCapacity)
	{
		return false;
	}

	char szRes[256] = { 0 }, szDt[64] = {0};
	sprintf(szRes, szResFmt, nCode, (nCode == 200 ? "OK" : "error"), 
		SysUtil::GetFmtDateTime(szDt,sizeof(szDt)), *pnData);

	int nRes = strlen(szRes);

	memmove((uint8_t*)pData + nRes, pData, *pnData);
	memcpy(pData, szRes, nRes);

	*pnData += nRes;

	return true;
}

bool HttpUtil::IsHttpRequest(char *pData, int nData)
{
	char szFinds[][16] = { "HTTP", "User-Agent", "Accept", "Host", "Connection", "Cookie" };
	//char szFinds[][16] = { "Host"};
	for (int i = 0; i < sizeof(szFinds) / sizeof(szFinds[0]); i++)
	{
		if (!SysUtil::FindBytes(pData, nData, szFinds[i], strlen(szFinds[i]), 0))
			return false;
	}
	return true;
}

bool HttpUtil::IsHttpResponse(char *pData, int nData)
{
	char szFinds[][16] = { "HTTP", "Connection", "Content-Type", "Date", "Expires", "Cookie" };
	for (int i = 0; i < sizeof(szFinds) / sizeof(szFinds[0]); i++)
	{
		if (!SysUtil::FindBytes(pData, nData, szFinds[i], strlen(szFinds[i]), 0))
			return false;
	}
	return true;
}

bool HttpUtil::Parse302Url(char* pData, int nData, char szNewUrl[], int nNewUrl)
{
    char* p = (char*)MemStrX(pData, "302", nData >  100 ? 100: nData, sizeof("302"));
    //strstr(pData, "302",)
    return false;
}
